#ifndef _NE_GLSL_PROGRAM_H_
#define _NE_GLSL_PROGRAM_H_

#include <mathematics/Color.h>
#include <mathematics/Matrix4.h>
#include <system/ResourceManager.h>
#include <graphics/GLSLShader.h>

namespace ne
{
    class NEAPI GLSLProgram : public Resource, public GLObject, public ResourceManager
    {
        public:
            GLSLProgram(ResourceManager *pManager,
                        const uint64_t id,
                        const std::string &name,
                        const std::string &fileName,
                        const bool isManual,
                        ManualResourceLoader *pLoader);

            GLSLShaderPtr createShaderFromFile(const std::string &name,
                                               const std::string &fileName,
                                               const ShaderType type);

            bool attachShader(const GLSLShaderPtr &pShader);

            bool detachShader(const GLSLShaderPtr &pShader);

            void detachAll();

            bool useProgram(const bool use=true);

            bool link();

            bool validate();

            //TODO: Get & Set attrib and uniform after useProgram
            int getAttribLocation(const std::string &name) const;
            bool setAttribLocation(const uint32_t location, const std::string &name);

            int getUniformLocation(const std::string &name) const;

            bool setUniform(const int location, const int value);
            bool setUniform(const int location, const real value);
            bool setUniform(const int location, const Vector2 &value);
            bool setUniform(const int location, const Vector3 &value);
            bool setUniform(const int location, const Vector4 &value);
            bool setUniform(const int location, const Color &value);
            bool setUniform(const int location, const Quaternion &value);
            bool setUniform(const int location, const Matrix3 &value, const bool transpose=false);
            bool setUniform(const int location, const Matrix4 &value, const bool transpose=false);

            bool setUniform(const std::string &name, const int value);
            bool setUniform(const std::string &name, const real value);
            bool setUniform(const std::string &name, const Vector2 &value);
            bool setUniform(const std::string &name, const Vector3 &value);
            bool setUniform(const std::string &name, const Vector4 &value);
            bool setUniform(const std::string &name, const Color &value);
            bool setUniform(const std::string &name, const Quaternion &value);
            bool setUniform(const std::string &name, const Matrix3 &value, const bool transpose=false);
            bool setUniform(const std::string &name, const Matrix4 &value, const bool transpose=false);

            bool setUniformArray(const int location, const uint32_t count, const int *pValue);
            bool setUniformArray(const int location, const uint32_t count, const real *pValue);
            bool setUniformArray(const int location, const uint32_t count, const Vector2 *pValue);
            bool setUniformArray(const int location, const uint32_t count, const Vector3 *pValue);
            bool setUniformArray(const int location, const uint32_t count, const Vector4 *pValue);
            bool setUniformArray(const int location, const uint32_t count, const Color *pValue);
            bool setUniformArray(const int location, const uint32_t count, const Quaternion *pValue);
            bool setUniformArray(const int location, const uint32_t count, const Matrix3 *pValue, const bool transpose=false);
            bool setUniformArray(const int location, const uint32_t count, const Matrix4 *pValue, const bool transpose=false);

            bool setUniformArray(const std::string &name, const uint32_t count, const int *pValue);
            bool setUniformArray(const std::string &name, const uint32_t count, const real *pValue);
            bool setUniformArray(const std::string &name, const uint32_t count, const Vector2 *pValue);
            bool setUniformArray(const std::string &name, const uint32_t count, const Vector3 *pValue);
            bool setUniformArray(const std::string &name, const uint32_t count, const Vector4 *pValue);
            bool setUniformArray(const std::string &name, const uint32_t count, const Color *pValue);
            bool setUniformArray(const std::string &name, const uint32_t count, const Quaternion *pValue);
            bool setUniformArray(const std::string &name, const uint32_t count, const Matrix3 *pValue, const bool transpose=false);
            bool setUniformArray(const std::string &name, const uint32_t count, const Matrix4 *pValue, const bool transpose=false);

            bool isProgram() const;

            bool isLinked() const;

            bool isValidated() const;

            bool isShaderAttached(const GLSLShaderPtr &pShader) const;

            std::string getInfoLog() const;

            virtual ResourcePtr prepare(const std::string &name,
                                        const std::string &fileName,
                                        const bool isManual=false,
                                        ManualResourceLoader *pLoader=0,
                                        const bool backgroundThread=false);

            virtual ResourcePtr load(const std::string &name,
                                     const std::string &fileName,
                                     const bool isManual=false,
                                     ManualResourceLoader *pLoader=0,
                                     const bool backgroundThread=false);

            virtual bool prepare(const bool backgroundThread=false);
            
            virtual bool load(const bool backgroundThread=false);

            virtual ~GLSLProgram();

        protected:
            virtual bool loadImplement();

            virtual bool unloadImplement();

            virtual bool downloadImplement();

            virtual size_t _calculateSize() const;

        protected:
            Resource* createImplement(const uint64_t id,
                                      const std::string &name,
                                      const std::string &fileName,
                                      const bool isManual,
                                      ManualResourceLoader *pLoader);

        private:
            typedef std::map<uint32_t, GLSLShader*> AttachedShaders;
            typedef AttachedShaders::iterator AttachedShadersIterator;
            typedef AttachedShaders::const_iterator AttachedShadersConstIterator;
            AttachedShaders mAttachedShaders;
    };

    class NEAPI GLSLProgramPtr : public SharedPtr<GLSLProgram>
    {
        public:
            GLSLProgramPtr() : SharedPtr<GLSLProgram>() {}
            explicit GLSLProgramPtr(GLSLProgram *pProgram) : SharedPtr<GLSLProgram>(pProgram) {}
            GLSLProgramPtr(const GLSLProgramPtr &pProgram) : SharedPtr<GLSLProgram>(pProgram) {}

            GLSLProgramPtr(const ResourcePtr &p)
                : SharedPtr<GLSLProgram>()
            {
                mpPtr = static_cast<GLSLProgram*>(p.ptr());
                mpUseCount = p.getUseCountPtr();
                if (mpUseCount)
                {
                    ++(*mpUseCount);
                }
            }

            GLSLProgramPtr& operator = (const ResourcePtr &p)
            {
                if (mpPtr == static_cast<GLSLProgram*>(p.ptr()))
                {
                    return *this;
                }
                GLSLProgramPtr temp(p);
                swap(temp);
                return *this;
            }
    };
}

#endif
